package p2p.logic.connection.communication.message;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import p2p.logic.connection.Command;
import p2p.logic.connection.ConnectionOwner;
import p2p.logic.connection.client.Client;
import p2p.logic.exceptions.InvalidIpException;
import p2p.logic.util.Utils;

public class Message {
	
	private UUID uuid;
	private int sequenceNumber;
	private int totalNumber;
	private boolean isAck;
	private int command;
	private byte[] payload;
	
	private int senderPort;
	private String senderIp;
	
	private int recipientPort;
	private String recipientIp;
	
	private Command commandObject;
	private byte numOfHosts = 0;
	
	public Message(ConnectionOwner owner, String recipientIp, int recipientPort, Command command){
		command.setMessage(this);
		
		this.commandObject = command;
		this.senderIp = owner.getGateway().getIp();
		this.senderPort = owner.getGateway().getPort();
		
		this.recipientIp = recipientIp;
		this.recipientPort = recipientPort;
		
		this.uuid = Utils.newUUID();
		this.totalNumber = 1;
		this.sequenceNumber = 1;
		this.isAck = false;
		this.command = command.getCommandCode();
		this.fillPayload(this.senderIp, this.senderPort);
		
	}
	
	public Message(String senderIp, int senderPort, String recipientIp, int recipientPort, Command command){
		
		command.setMessage(this);
		this.commandObject = command;
		
		this.senderIp = senderIp;
		this.senderPort = senderPort;
		
		this.recipientIp = recipientIp;
		this.recipientPort = recipientPort;
		
		this.uuid = Utils.newUUID();
		this.totalNumber = 1;
		this.sequenceNumber = 1;
		this.isAck = false;
		this.command = command.getCommandCode();
		this.fillPayload(senderIp, senderPort);
	
	}

	//Ack
	public Message(String senderIp, int senderPort, String recipientIp, int recipientPort, UUID uuid){
		
		this.uuid = uuid;
		
		this.senderIp = senderIp;
		this.senderPort = senderPort;
		
		this.recipientIp = recipientIp;
		this.recipientPort = recipientPort;
		
		this.uuid = uuid;
		
		this.totalNumber = 1;
		this.sequenceNumber = 1;
		this.isAck = true;
		
		fillPayload();
		
	}
	
	public Message(String ip, int port, byte[] payload){
		
		this.senderIp = ip;
		this.senderPort = port;
		this.payload = payload;
		this.uuid = Utils.toUUID(Utils.subArray(payload, 0, 16));
		this.command = this.payload[16];
		this.sequenceNumber = Utils.byteArrayToInt(Utils.subArray(this.payload, 18, 20));
		this.totalNumber = Utils.byteArrayToInt(Utils.subArray(this.payload, 20, 22));
	
	}
	
	public Message(byte[] payload){
		
		try{
			
			this.payload = payload;
			this.uuid = Utils.toUUID(Utils.subArray(payload, 0, 16));
			this.command = this.payload[16];
			this.isAck = this.payload[17] > 0 ? true : false;
			this.sequenceNumber = Utils.byteArrayToInt(Utils.subArray(this.payload, 18, 20));
			this.totalNumber = Utils.byteArrayToInt(Utils.subArray(this.payload, 20, 22));
			
			this.senderIp = String.valueOf(Utils.byteArrayToIp(Utils.subArray(this.payload, 22, 26)));
			this.senderPort = Utils.byteArrayToInt(Utils.subArray(this.payload, 26, 28));
			
			this.recipientIp = String.valueOf(Utils.byteArrayToIp(Utils.subArray(this.payload, 28, 32)));
			this.recipientPort = Utils.byteArrayToInt(Utils.subArray(this.payload, 32, 34));
		
			this.numOfHosts = this.payload[34];
			
		}catch (InvalidIpException e){
			
		}
	}

	public UUID getUuid() {
		return uuid;
	}

	public int getSequenceNumber() {
		return sequenceNumber;
	}

	public int getTotalNumber() {
		return totalNumber;
	}

	public byte[] getPayload() {
		return payload;
	}

	public int getCommand() {
		return command;
	}

	public boolean isAck() {
		return isAck;
	}
	
	public int getSenderPort() {
		return senderPort;
	}

	public String getSenderIp() {
		return senderIp;
	}
	
	private void fillPayload(){
		try {
			
			this.payload = new byte[35];
			
			this.payload = Utils.insertArray(this.payload, Utils.uuidToByteArray(getUuid()), 0);
			
			this.payload[16] = (byte) getCommand();
			this.payload[17] = (byte) (isAck() ? 1 : 0);
			
			this.payload = Utils.insertArray(this.payload, Utils.intToByteArray(getSequenceNumber()), 18);
			
			this.payload = Utils.insertArray(this.payload, Utils.intToByteArray(getTotalNumber()), 20);
			
			byte[] senderIp = Utils.ipToByteArray(this.senderIp);
			byte[] senderPort = Utils.intToByteArray(this.senderPort);
			
			byte[] senderInfo = new byte[6];
			senderInfo = Utils.insertArray(senderInfo, senderIp, 0);
			senderInfo = Utils.insertArray(senderInfo, senderPort, 4);
			this.payload = Utils.insertArray(this.payload, senderInfo, 22);
			
			byte[] remoteIp = Utils.ipToByteArray(this.recipientIp);
			byte[] remotePort = Utils.intToByteArray(this.recipientPort);
			
			byte[] remoteInfo = new byte[6];
			remoteInfo = Utils.insertArray(remoteInfo, remoteIp, 0);
			remoteInfo = Utils.insertArray(remoteInfo, remotePort, 4);
			
			this.payload = Utils.insertArray(this.payload, remoteInfo, 28);
					
			this.payload[34] = 0;
			
		} catch (InvalidIpException e) {
			
			e.printStackTrace();
		
		}
	}

	private void fillPayload(String ip, int port){
		
		this.payload = new byte[1024];
		
		try {
			
			byte[] localIp = Utils.ipToByteArray(this.senderIp);
			byte[] localPort = Utils.intToByteArray(this.senderPort);
			
			byte[] senderInfo = new byte[6];
			senderInfo = Utils.insertArray(senderInfo, localIp, 0);
			senderInfo = Utils.insertArray(senderInfo, localPort, 4);
			
			byte[] remoteIp = Utils.ipToByteArray(this.recipientIp);
			byte[] remotePort = Utils.intToByteArray(this.recipientPort);
			
			byte[] remoteInfo = new byte[6];
			remoteInfo = Utils.insertArray(remoteInfo, remoteIp, 0);
			remoteInfo = Utils.insertArray(remoteInfo, remotePort, 4);
			
			this.payload = Utils.insertArray(this.payload, Utils.uuidToByteArray(getUuid()), 0);
			
			this.payload[16] = (byte) getCommand();
			this.payload[17] = (byte) (isAck() ? 1 : 0);
			
			this.payload = Utils.insertArray(this.payload, Utils.intToByteArray(getSequenceNumber()), 18);
			
			this.payload = Utils.insertArray(this.payload, Utils.intToByteArray(getTotalNumber()), 20);
			
			this.payload = Utils.insertArray(this.payload, senderInfo, 22);
			this.payload = Utils.insertArray(this.payload, remoteInfo, 28);
		
			
			this.payload[34] = 0;
			
			
		} catch (InvalidIpException e) {
		
			e.printStackTrace();
		
		}
			
	}
	
	public void setCommand(int command) {
		this.command = command;
	}


	public int getRecipientPort() {
		return recipientPort;
	}

	public String getRecipientIp() {
		return recipientIp;
	}
	
	public Command getCommandObject(){
		return this.commandObject;
	}
	
	@Override
	public String toString() {
		return this.uuid + "|" + this.command + "|" + this.isAck + "|" + this.sequenceNumber + "|" + this.totalNumber;
	}
	
	public void addClientToPayload(Client client){
		
		byte[] clientInfo = new byte[6];
		
		try {
		
			clientInfo = Utils.insertArray(clientInfo, Utils.ipToByteArray(client.getIp()), 0);
			clientInfo = Utils.insertArray(clientInfo, Utils.intToByteArray(client.getPort()), 4);
			this.payload = Utils.insertArray(this.payload, clientInfo, this.numOfHosts);
			
			this.numOfHosts++;
			
			this.payload[34] = this.numOfHosts;
			
		} catch (InvalidIpException e) {
			e.printStackTrace();
		}
		
		
		
	}
	
	public boolean isBroadCast(){
		return this.command == 3 ? true : false;
	}
	
	public List<Client> clientList(){
		
		List<Client> result = new ArrayList<Client>();
		
		for (int i = 0; i < this.payload[34]; i++){
			
			try {
				int start = i * 6;
				
				byte[] ipArr = Utils.subArray(this.payload, start, start+4);
				String ip = Utils.byteArrayToIp(ipArr);
				start = start+4;
				
				byte[] portArr = Utils.subArray(this.payload, start, start+2);
				int port = Utils.byteArrayToInt(portArr);
				
				Client c = new Client(ip, port);
				result.add(c);
				
			} catch (InvalidIpException e) {
				e.printStackTrace();
			}
		}
		return result;
	}

	public void setUuid(UUID uuid) {
		this.uuid = uuid;
		this.payload = Utils.insertArray(this.payload, Utils.uuidToByteArray(getUuid()), 0);
		fillPayload(this.senderIp,this.recipientPort);

	}
	
	
	
	
	

}
